/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>

#include "celix_compiler.h"
#include "celix_threads.h"
#include "celix_utils.h"

#ifndef CELIX_UTILS_STATIC_DEFINE
const celix_thread_t celix_thread_default = {0, 0};
#endif

celix_status_t celixThread_create(celix_thread_t *new_thread, const celix_thread_attr_t *attr, celix_thread_start_t func, void *data) {
    celix_status_t status = CELIX_SUCCESS;

    if (pthread_create(&(*new_thread).thread, attr, func, data) != 0) {
        status = CELIX_BUNDLE_EXCEPTION;
    }
    else {
        __atomic_store_n(&(*new_thread).threadInitialized, true, __ATOMIC_RELEASE);
    }

    return status;
}

#if defined(_GNU_SOURCE) && defined(__linux__) && !defined(__UCLIBC__)
void celixThread_setName(celix_thread_t *thread, const char *threadName) {
    pthread_setname_np(thread->thread, threadName);
}
#else
void celixThread_setName(celix_thread_t *thread CELIX_UNUSED, const char *threadName  CELIX_UNUSED) {
    //nop
}
#endif

// Returns void, since pthread_exit does exit the thread and never returns.
void celixThread_exit(void *exitStatus) {
    pthread_exit(exitStatus);
}

celix_status_t celixThread_detach(celix_thread_t thread) {
    return pthread_detach(thread.thread);
}

celix_status_t celixThread_join(celix_thread_t thread, void **retVal) {
    celix_status_t status = CELIX_SUCCESS;

    if (pthread_join(thread.thread, retVal) != 0) {
        status = CELIX_BUNDLE_EXCEPTION;
    }

    // #TODO make thread a pointer? Now this statement has no effect
    // thread.threadInitialized = false;

    return status;
}

celix_status_t celixThread_kill(celix_thread_t thread, int sig) {
    return pthread_kill(thread.thread, sig);
}

celix_thread_t celixThread_self() {
    celix_thread_t thread;

    thread.thread = pthread_self();
    thread.threadInitialized = true;

    return thread;
}

int celixThread_equals(celix_thread_t thread1, celix_thread_t thread2) {
    return pthread_equal(thread1.thread, thread2.thread);
}

bool celixThread_initialized(celix_thread_t thread) {
    return __atomic_load_n(&thread.threadInitialized, __ATOMIC_ACQUIRE);
}


celix_status_t celixThreadMutex_create(celix_thread_mutex_t *mutex, celix_thread_mutexattr_t *attr) {
    return pthread_mutex_init(mutex, attr);
}

celix_status_t celixThreadMutex_destroy(celix_thread_mutex_t *mutex) {
    return pthread_mutex_destroy(mutex);
}

celix_status_t celixThreadMutex_lock(celix_thread_mutex_t *mutex) {
    return pthread_mutex_lock(mutex);
}

celix_status_t celixThreadMutex_tryLock(celix_thread_mutex_t *mutex) {
    return pthread_mutex_trylock(mutex);
}

celix_status_t celixThreadMutex_unlock(celix_thread_mutex_t *mutex) {
    return pthread_mutex_unlock(mutex);
}

celix_status_t celixThreadMutexAttr_create(celix_thread_mutexattr_t *attr) {
    return pthread_mutexattr_init(attr);
}

celix_status_t celixThreadMutexAttr_destroy(celix_thread_mutexattr_t *attr) {
    return pthread_mutexattr_destroy(attr);
}

celix_status_t celixThreadMutexAttr_settype(celix_thread_mutexattr_t *attr, int type) {
    celix_status_t status;
    switch(type) {
        case CELIX_THREAD_MUTEX_NORMAL :
            status = pthread_mutexattr_settype(attr, PTHREAD_MUTEX_NORMAL);
            break;
        case CELIX_THREAD_MUTEX_RECURSIVE :
            status = pthread_mutexattr_settype(attr, PTHREAD_MUTEX_RECURSIVE);
            break;
        case CELIX_THREAD_MUTEX_ERRORCHECK :
            status = pthread_mutexattr_settype(attr, PTHREAD_MUTEX_ERRORCHECK);
            break;
        case CELIX_THREAD_MUTEX_DEFAULT :
            status = pthread_mutexattr_settype(attr, PTHREAD_MUTEX_DEFAULT);
            break;
        default:
            status = pthread_mutexattr_settype(attr, PTHREAD_MUTEX_DEFAULT);
            break;
    }
    return status;
}

celix_status_t celixThreadCondition_init(celix_thread_cond_t *condition, celix_thread_condattr_t *attr) {
#ifdef __APPLE__
    return pthread_cond_init(condition, attr);
#else
    celix_status_t status = CELIX_SUCCESS;
    if (attr) {
        status = pthread_condattr_setclock(attr, CLOCK_MONOTONIC);
        status = CELIX_DO_IF(status, pthread_cond_init(condition, attr));
    } else {
        celix_thread_condattr_t condattr;
        (void)pthread_condattr_init(&condattr); // always return 0
        status = pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);
        status = CELIX_DO_IF(status, pthread_cond_init(condition, &condattr));
        (void)pthread_condattr_destroy(&condattr); // always return 0
    }
    return status;
#endif
}

celix_status_t celixThreadCondition_destroy(celix_thread_cond_t *condition) {
    return pthread_cond_destroy(condition);
}

celix_status_t celixThreadCondition_wait(celix_thread_cond_t *cond, celix_thread_mutex_t *mutex) {
    return pthread_cond_wait(cond, mutex);
}

#ifdef __APPLE__
celix_status_t celixThreadCondition_timedwaitRelative(celix_thread_cond_t *cond, celix_thread_mutex_t *mutex, long seconds, long nanoseconds) {
    struct timespec time;
    time.tv_sec = seconds;
    time.tv_nsec = nanoseconds;
    return pthread_cond_timedwait_relative_np(cond, mutex, &time);
}
#else
celix_status_t celixThreadCondition_timedwaitRelative(celix_thread_cond_t *cond, celix_thread_mutex_t *mutex, long seconds, long nanoseconds) {
    double delay = (double)seconds + ((double)nanoseconds / 1000000000);
    struct timespec time = celixThreadCondition_getDelayedTime(delay);
    return pthread_cond_timedwait(cond, mutex, &time);
}
#endif

struct timespec celixThreadCondition_getTime() {
    return celixThreadCondition_getDelayedTime(0);
}

struct timespec celixThreadCondition_getDelayedTime(double delayInSeconds) {
    struct timespec now = celix_gettime(CLOCK_MONOTONIC);
    if (delayInSeconds == 0) {
        return now;
    }
    return celix_delayedTimespec(&now, delayInSeconds);
}

celix_status_t
celixThreadCondition_waitUntil(celix_thread_cond_t* cond, celix_thread_mutex_t* mutex, const struct timespec* absTime) {
    if (absTime == NULL) {
        return CELIX_ILLEGAL_ARGUMENT;
    }
#if __APPLE__
    struct timespec now = celix_gettime(CLOCK_MONOTONIC);
    double diff = celix_difftime(&now, absTime);
    if (diff <= 0) {
        return ETIMEDOUT;
    }
    long seconds = diff;
    long nanoseconds = (diff - seconds) * CELIX_NS_IN_SEC;
    return celixThreadCondition_timedwaitRelative(cond, mutex, seconds, nanoseconds);
#else
    return pthread_cond_timedwait(cond, mutex, absTime);
#endif
}

celix_status_t celixThreadCondition_broadcast(celix_thread_cond_t *cond) {
    return pthread_cond_broadcast(cond);
}

celix_status_t celixThreadCondition_signal(celix_thread_cond_t *cond) {
    return pthread_cond_signal(cond);
}

celix_status_t celixThreadRwlock_create(celix_thread_rwlock_t *lock, celix_thread_rwlockattr_t *attr) {
    return pthread_rwlock_init(lock, attr);
}

celix_status_t celixThreadRwlock_destroy(celix_thread_rwlock_t *lock) {
    return pthread_rwlock_destroy(lock);
}

celix_status_t celixThreadRwlock_readLock(celix_thread_rwlock_t *lock) {
    return pthread_rwlock_rdlock(lock);
}

celix_status_t celixThreadRwlock_tryReadLock(celix_thread_rwlock_t *lock) {
    return pthread_rwlock_tryrdlock(lock);
}

celix_status_t celixThreadRwlock_writeLock(celix_thread_rwlock_t *lock) {
    return pthread_rwlock_wrlock(lock);
}

celix_status_t celixThreadRwlock_tryWriteLock(celix_thread_rwlock_t *lock) {
    return pthread_rwlock_trywrlock(lock);
}

celix_status_t celixThreadRwlock_unlock(celix_thread_rwlock_t *lock) {
    return pthread_rwlock_unlock(lock);
}

celix_status_t celixThreadRwlockAttr_create(celix_thread_rwlockattr_t *attr) {
    return pthread_rwlockattr_init(attr);
}

celix_status_t celixThreadRwlockAttr_destroy(celix_thread_rwlockattr_t *attr) {
    return pthread_rwlockattr_destroy(attr);
}

celix_status_t celixThread_once(celix_thread_once_t *once_control, void (*init_routine)(void)) {
    return pthread_once(once_control, init_routine);
}

celix_status_t celix_tss_create(celix_tss_key_t* key, void (*destroyFunction)(void*)) {
    return pthread_key_create(key, destroyFunction);
}

celix_status_t celix_tss_delete(celix_tss_key_t key) {
    return pthread_key_delete(key);
}

celix_status_t celix_tss_set(celix_tss_key_t key, void* value) {
    return pthread_setspecific(key, value);
}

void* celix_tss_get(celix_tss_key_t key) {
    return pthread_getspecific(key);
}
